// gaz.lite.js - some lite extensions to jQuery event handling.
//
//   Copyright © 2007 Studio Associato Di Nunzio e Di Gregorio
//
//   Authors:
//      Federico Di Gregorio <fog@initd.org>
//      Pierluidi Di Nunzio  <gg@initd.org>
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU Lesser General Public Licenseas published by
// the Free Software Foundation, either version 3 of the License, or (at 
// your option) any later version. See the file Documentation/LGPL3 in the
// original distribution for details. There is ABSOLUTELY NO warranty.

;(function($) {
    var Gaz = window.Gaz || (window.Gaz = {});
    
    /* private utility functions */

    // Here we do the bare minimum to provide an event object to the handler.
    // Note that this is a subset of the fixes implemented in jQuery; what we
    // consider strictly necessary for handlers that need the "lite"
    // functionality (mostly drag&drop implementor that want to process
    // a lot of mousemove events.) Note how we monkey patch to provide the
    // right handler-making function at runtime.
    
    function makeFirefoxLiteHandler(elem, handler, data) {
        return function(evt) {
            evt = arguments[0] = evt || window.event;
            evt.data = data;
            handler.apply(elem, arguments);
        }
    }

    function makeIE6LiteHandler(elem, handler, data) {
        return function(evt) {
                evt = arguments[0] = evt || window.event;
                evt.data = data;                
                evt.preventDefault = function() { this.returnValue = false; };
                evt.stopPropagation = function() { this.cancelBubble = true; };
                evt.target = evt.srcElement || document;
                handler.apply(elem, arguments);
            }
    }

    function makeIE7LiteHandler(elem, handler, data) {
        return function(evt) {
            evt = arguments[0] = evt || window.event;
            evt.data = data;
            evt.target = evt.srcElement || document;
            handler.apply(elem, arguments);
        }
    }

    function makeGenericLiteHandler(elem, handler, data) {
        return function(evt) {
            evt = arguments[0] = evt || window.event;
            evt.data = data;
            
            if (!evt.preventDefault)
                evt.preventDefault = function() { this.returnValue = false; };
            if (!evt.stopPropagation)
                evt.stopPropagation = function() { this.cancelBubble = true; };

            if (!evt.target)
                evt.target = evt.srcElement || document;
            if (evt.target.nodeType == 3 )
                evt.target = evt.target.parentNode;
            if (!evt.relatedTarget && evt.fromElement)
                evt.relatedTarget = evt.fromElement == evt.target ? evt.toElement : evt.fromElement;
        
            handler.apply(elem, arguments);
        }
    }
    
    /* public functions */
    
    Gaz.lite = {
    
        // Function: bind
        // Binds events with minimum overhead. This function has the same semantics
        // of jQuery's .bind() but at the cost of many limitiations is much faster.
        // Limitations: no multiple events in a single call, and much
        // reduced event object.
        
        bind: function(elem, type, handler, data) {
            
            handler.liteHandler = function(evt) {
                // Here we do the bare minimum to provide an event object to the handler.
                // Note that this is a subset of the fixes implemented in jQuery; what we
                // consider strictly necessary for handlers that need the "lite"
                // functionality (mostly drag&drop implementor that want to process
                // mousemove.)
                evt = arguments[0] = evt || window.event;
                evt.data = data;
                
                if (!evt.preventDefault)
                    evt.preventDefault = function() { this.returnValue = false; };
                if (!evt.stopPropagation)
                    evt.stopPropagation = function() { this.cancelBubble = true; };

                if (!evt.target)
                    evt.target = evt.srcElement || document;
                if (evt.target.nodeType == 3 )
                    evt.target = evt.target.parentNode;
            
                handler.apply(elem, arguments);
            }
            
            // Save a copy of the handler by event type and namespace.
            var jdata = jQuery.data(elem, "liteHandlers") || {};
            (jdata[type] = jdata[type] || []).push(handler);
            jQuery.data(elem, "liteHandlers", jdata);
            
            var realType = type.split('.')[0];
            if (elem.addEventListener)
                elem.addEventListener(realType, handler.liteHandler, false);
            else if (elem.attachEvent)
                elem.attachEvent('on'+realType, handler.liteHandler);
                
            elem = null;
        },
        
        unbind: function(elem, type, handler) {
            // If handler is null we unbind all handlers of the given type, else
            // we look for it and unbind even if it is not present in the jQuery
            // cached data.
            var handlers;
            var data = jQuery.data(elem, "liteHandlers");
            if (handler) {
                var index = data && data[type] && data[type].indexOf(handler);
                if (index >= 0)
                    data[type].splice(index, 1);
                handlers = [handler];
            }
            else {
                handlers = data && data[type];
                delete data[type];
            }
            
            if (handlers) {
                $(handlers).each(function() {
                    if (elem.removeEventListener)
                        elem.removeEventListener(type, this.liteHandler, false);
                    else if (elem.detachEvent)
                        elem.detachEvent('on'+type, this.liteHandler);
                    delete this.liteHandler;
                });
            }
                                    
            elem = null;
        }
    };
    
    /* lite extensions to jQuery */
    
    $.extend($.fn, {
        liteBind: function(type, data, handler) {
            return this.each(function() {
                if (typeof data == "function") {
                    handler = data;
                    data = null;
                }
                Gaz.lite.bind(this, type, handler, data);
            });
        },
        
        liteUnbind: function(type, handler) {
            return this.each(function() {
                Gaz.lite.unbind(this, type, handler);
            });
        }
    });
})(jQuery);